/**
 * @fileOverview Prototype Profiler (pro2) is component, dedicated to profiling 
 * domReady queue and selectors logging in Prototype framework.
**/

/**
 * Creates a profiler
 *
 * @param {Object} options Profiler options: callbacks & namespace
 * @constructor
 * @class Prototype Profiler
 */
PrototypeProfiler = function(options) {
	var obj = this;
	
	/**
	 * ignore Ignore all selectors if true.
	 * @type Boolean
	 * @private
	*/
	this.ignore = false;
	
	/**
	 * selectors List of used selectors grouped by selector type
	 * @type Object{}
	 * @public
	*/
	this.selectors = {};

	/**
	 * totalQueries Total count of tracked selectors.
	 * @type Integer
	 * @public
	*/
	this.totalQueries = 0,

	/**
	 * events Array with dom:loaded events.
	 * @type Object[]
	 * @public
	*/
	this.events = [];

	/**
	 * options Profiler options: callbacks & namespace.
	 * @type Object
	 * @public
	*/
	this.options = {};

	this.options = Object.extend(this.options, options);
	if (this.options.namespace) {
		this.options.namespaceObj = eval(this.options.namespace);
		if (typeof(this.options.namespace) !== 'string')
			this.options.namespace = '[namespace]';
	}
	
	/**
	 * @function
	 * @description Initializing profiler: wraping prototype functions
	 * @private
	*/
	function init() {
		// injecting log function into the prototype selector engine (findChildElements is used in all functions, like $$, select, up, down, etc)
		Selector.findChildElements = Selector.findChildElements.wrap(function(proceed, element, expressions) {
			var temp = expressions.join(','),
				results = proceed(element, expressions);
			
			if (!obj.ignore)
				obj.addSelectorToLog(temp, results.size());
		    return results;
		});
		
		$ = $.wrap(function(proceed, element) {
			var results = proceed(element);
			if (Object.isString(element) && !obj.ignore)
				obj.addSelectorToLog("#" + element, (results ? 1 : 0));

			return results;
		});

		document.observe = document.observe.wrap(function(proceed, eventName, handler) {
			if (eventName == 'dom:loaded') {
				var num = obj.logOnLoadEvent((handler.__method || handler));

				var func = function() {
					obj.currentEvent = num;
					var start = (new Date).getTime();
					handler.call(handler, arguments);

					obj.events[num].time = (new Date).getTime() - start;

					if (obj.options.onOnLoadEventAdded)
						obj.options.onOnLoadEventAdded(obj);
					
					obj.currentEvent = null;
				};
			}

			return proceed(eventName, func);
		});
		
		Function.prototype._bind = Function.prototype.bind; 
		Function.prototype.bind = function() {
			var f = this._bind.apply(this, arguments);
			f.__method = this; 
			return f; 
		};

		Function.prototype._bindAsEventListener = Function.prototype.bindAsEventListener; 
		Function.prototype.bindAsEventListener = function() {
			var f = this._bindAsEventListener.apply(this, arguments);
			f.__method = this; 
			return f; 
		}
	  
	}

	/**
	 * @function
	 * @description Add passed function into the log.
	 * @param {Function} func 
	 * @private
	*/
	this.logOnLoadEvent = function(func) {
		function traverseNamespace(path, target) {
			for (var x in target) {
				try {
					var cur = target[x];
					if (!cur)
						continue;
					
					if (cur.toString() === '[object Object]') {
						var found = traverseNamespace(path + '.' + x, cur);
						if (found)
							return found;
					} else
					if (typeof(cur) === 'function' && cur == func)
						return path + '.' + x;
				} catch(e) {
				} 
			}
			return false;
		}
		
		var funcDetailed = '';
		if (func.name)
			funcDetailed =func.name + '(' + func.argumentNames().join(', ') + ')';
		else {
			var fname = false;

			if (this.options.namespace)
				fname = traverseNamespace(this.options.namespace, this.options.namespaceObj);
			
			if (fname)
				funcDetailed = fname  + '(' + func.argumentNames().join(', ') + ')';
			else
				funcDetailed = (func + '').substr(0, 150);
		}
		
		var num = this.events.push({name: funcDetailed, time: null, selectors: []});

		if (this.options.onOnLoadEventAdded)
			this.options.onOnLoadEventAdded(this);
		// returning index of event, that we've just pushed;
		return num - 1;
	}

	/**
	 * @function
	 * @description Add selector into the log.
	 * @param {String} Selector
	 * @private
	*/
	this.addSelectorToLog = function(selector, count) {
		var unified, e, p, m, ps = Selector.patterns, selectors = [],
			replacePatterns = {	
								laterSibling: '~',
								child: '>',
								adjacent: '+',
							    descendant:   ' ',
								tagName: function(m) {return m[1] == '*' ? m[1] : 'element'}, 
								id: '#id', 
								className: '.class', 
								pseudo: function(m) {return ':' + m[1] + '(whatever)';},
								attrPresence: '[attr]',
								attr: function(m) {return '[attr' + m[2] + 'whatever]';}
							};


		expressions = Selector.split(selector);
		for (var k = 0, l = expressions.length; k < l; k++) { 
			e = expressions[k].strip();
			var le = null, chunk = '';
			while (e && le != e && (/\S/).test(e)) {
				le = e;
				for (var i in replacePatterns) {
					p = ps[i];
					if (m = e.match(p)) {
		          		e = e.replace(m[0], '');

						chunk += typeof(replacePatterns[i]) == 'string' ? replacePatterns[i] : replacePatterns[i](m);
						break;
					}
				}
			}
			selectors.push(chunk);
		}
		
		unified = selectors.join(', ');

		if (!obj.selectors[unified])
			obj.selectors[unified] = [];

		if (obj.currentEvent != null)
			obj.events[obj.currentEvent].selectors.push(selector);
		
		obj.selectors[unified].push({'query': selector, 'count': count});
		obj.totalQueries++;

		obj.ignore = true;
		if (obj.options.onSelectorAdded)
			obj.options.onSelectorAdded(obj);
		obj.ignore = false;
	}

	init();
}